/*-------------------------------------------------------------------------
 *
 * pg_stat_statements.c
 *		跟踪整个数据库集群的语句规划和执行时间以及资源
 *		使用情况。
 *
 * 执行成本按每个不同来源查询总计，并保存在
 * 共享哈希表中。 （我们仅跟踪足够多的不同查询以适应指定的
 * 共享内存量。）
 *
 * 从 Postgres 9.2 开始，此模块对查询条目进行了归一化。 在
 * Postgres 14 中，归一化由核心完成（如果启用了 compute_query_id），
 * 或选择由第三方模块完成。
 *
 * 为了便于向用户展示条目，我们创建“代表性”查询
 * 字符串，其中常量被参数符号（$n）替代，以便更清楚
 * 地表示归一化条目可以表示的内容。 为了节省共享
 * 内存，并避免截断超大的查询字符串，我们将这些字符串
 * 存储在临时的外部查询文本文件中。 这些文件的偏移量
 * 存储在共享内存中。
 *
 * 关于锁定问题的说明：要在共享
 * 哈希表中创建或删除条目，必须独占持有 pgss->lock。 
 * 修改条目中的任何字段（除了计数器）也需要相同的操作。 
 * 查找条目时，必须共享持有锁。 
 * 要在条目中读取或更新计数器，必须共享或独占持有锁（以便条目不会消失！），
 * 还必须获取条目的互斥自旋锁。
 * 共享状态变量 pgss->extent（外部查询文本文件中的下一个可用位置）
 * 应仅在持有 pgss->mutex 自旋锁或 pgss->lock 的独占锁时访问。 
 * 我们使用互斥锁以便在仅持有 pgss->lock 的共享锁的情况下保留文件空间。 
 * 重写整个外部查询文本文件，例如用于垃圾收集，
 * 需要独占持有 pgss->lock；这允许在仅持有共享锁的情况下读取或写入文件中的单个条目。
 *
 *
 * Copyright (c) 2008-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/pg_stat_statements/pg_stat_statements.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>
#include <sys/stat.h>
#include <unistd.h>

#include "access/parallel.h"
#include "catalog/pg_authid.h"
#include "common/hashfn.h"
#include "executor/instrument.h"
#include "funcapi.h"
#include "jit/jit.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
#include "parser/scanner.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/shmem.h"
#include "storage/spin.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/queryjumble.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"

PG_MODULE_MAGIC;

/* 永久统计文件的位置（当数据库关闭时有效） */
#define PGSS_DUMP_FILE	PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat"

/*
 * 外部查询文本文件的位置。
 */
#define PGSS_TEXT_FILE	PG_STAT_TMP_DIR "/pgss_query_texts.stat"

/* 识别统计文件格式的魔法数字 */
static const uint32 PGSS_FILE_HEADER = 0x20220408;

/* PostgreSQL 主版本号，变化会使所有条目失效 */
static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100;

/* XXX: USAGE_EXEC 应该反映执行时间和/或缓冲区使用情况吗？ */
#define USAGE_EXEC(duration)	(1.0)
#define USAGE_INIT				(1.0)	/* 包括初始计划 */
#define ASSUMED_MEDIAN_INIT		(10.0)	/* 初始假定的中位数使用量 */
#define ASSUMED_LENGTH_INIT		1024	/* 初始假定的平均查询长度 */
#define USAGE_DECREASE_FACTOR	(0.99)	/* 每次条目释放时减少 */
#define STICKY_DECREASE_FACTOR	(0.50)	/* 粘性条目的因子 */
#define USAGE_DEALLOC_PERCENT	5	/* 一次释放这个 % 的条目 */
#define IS_STICKY(c)	((c.calls[PGSS_PLAN] + c.calls[PGSS_EXEC]) == 0)

/*
 * pgss_ProcessUtility 和 pgss_post_parse_analyze
 * 忽略的实用语句。
 */
#define PGSS_HANDLED_UTILITY(n)		(!IsA(n, ExecuteStmt) && \
									!IsA(n, PrepareStmt) && \
									!IsA(n, DeallocateStmt))

/*
 * 扩展版本号，用于支持旧版本扩展对象
 */
typedef enum pgssVersion
{
	PGSS_V1_0 = 0,
	PGSS_V1_1,
	PGSS_V1_2,
	PGSS_V1_3,
	PGSS_V1_8,
	PGSS_V1_9,
	PGSS_V1_10
} pgssVersion;

typedef enum pgssStoreKind
{
	PGSS_INVALID = -1,

	/*
	 * PGSS_PLAN 和 PGSS_EXEC 必须分别为 0 和 1，因为它们用于
	 * 引用 Counters 结构中数组的基础值，
	 * 并且此顺序在 pg_stat_statements_internal() 中是必需的。
	 */
	PGSS_PLAN = 0,
	PGSS_EXEC,

	PGSS_NUMKIND				/* 必须是此枚举的最后一个值 */
} pgssStoreKind;

/*
 * 定义哈希表条目身份的哈希表键。我们按用户和数据库分离
 * 查询，即使它们在其他方面是相同的。
 *
 * 如果您向此结构添加新键，请确保教会 pgss_store() 
 * 将填充字节清零。否则，事情会出错，因为 pgss_hash 
 * 使用 HASH_BLOBS 创建，因此 tag_hash 被用于哈希这个。
 */
typedef struct pgssHashKey
{
	Oid			userid;			/* 用户 OID */
	Oid			dbid;			/* 数据库 OID */
	uint64		queryid;		/* 查询标识符 */
	bool		toplevel;		/* 在顶层执行的查询 */
} pgssHashKey;

/*
 * pgssEntry 中保留的实际统计计数器。
 */
typedef struct Counters
{
	int64		calls[PGSS_NUMKIND];	/* 计划/执行的次数 */
	double		total_time[PGSS_NUMKIND];	/* 总计划/执行时间，
											 * 单位为毫秒 */
	double		min_time[PGSS_NUMKIND]; /* 最小计划/执行时间，
										 * 单位为毫秒 */
	double		max_time[PGSS_NUMKIND]; /* 最大计划/执行时间，
										 * 单位为毫秒 */
	double		mean_time[PGSS_NUMKIND];	/* 平均计划/执行时间，
											 * 单位为毫秒 */
	double		sum_var_time[PGSS_NUMKIND]; /* 计划/执行时间方差的总和，
											 * 单位为毫秒 */
	int64		rows;			/* 检索或影响的总行数 */
	int64		shared_blks_hit;	/* 共享缓冲区命中次数 */
	int64		shared_blks_read;	/* 读取的共享磁盘块数 */
	int64		shared_blks_dirtied;	/* 被修改的共享磁盘块数 */
	int64		shared_blks_written;	/* 写入的共享磁盘块数 */
	int64		local_blks_hit; /* 本地缓冲区命中次数 */
	int64		local_blks_read;	/* 读取的本地磁盘块数 */
	int64		local_blks_dirtied; /* 被修改的本地磁盘块数 */
	int64		local_blks_written; /* 写入的本地磁盘块数 */
	int64		temp_blks_read; /* 读取的临时块数 */
	int64		temp_blks_written;	/* 写入的临时块数 */
	double		blk_read_time;	/* 读取块所花费的时间，单位为毫秒 */
	double		blk_write_time; /* 写入块所花费的时间，单位为毫秒 */
	double		temp_blk_read_time; /* 读取临时块所花费的时间，单位为毫秒 */
	double		temp_blk_write_time;	/* 写入临时块所花费的时间，单位为
										 * 毫秒 */
	double		usage;			/* 使用因子 */
	int64		wal_records;	/* 生成的 WAL 记录数 */
	int64		wal_fpi;		/* 生成的 WAL 完整页面图像数 */
	uint64		wal_bytes;		/* 生成的 WAL 总量（字节） */
	int64		jit_functions;	/* 生成的 JIT 函数总数 */
	double		jit_generation_time;	/* 生成 JIT 代码的总时间 */
	int64		jit_inlining_count; /* 内联时间大于 0 的次数 */
	double		jit_inlining_time;	/* 内联 JIT 代码的总时间 */
	int64		jit_optimization_count; /* 优化时间大于 0 的次数 */
	double		jit_optimization_time;	/* 优化 JIT 代码的总时间 */
	int64		jit_emission_count; /* 发射时间大于 0 的次数 */
	double		jit_emission_time;	/* 发射 JIT 代码的总时间 */
} Counters;

/*
 * pg_stat_statements 的全局统计信息
 */
typedef struct pgssGlobalStats
{
	int64		dealloc;		/* 释放条目的次数 */
	TimestampTz stats_reset;	/* 所有统计信息重置时的时间戳 */
} pgssGlobalStats;

/*
 * 每条语句的统计信息
 *
 * 注意：如果查询文本文件的垃圾收集失败，
 * 我们将 query_offset 重置为零，query_len 重置为 -1。 这将被
 * qtext_fetch() 视为无效状态。
 */
typedef struct pgssEntry
{
	pgssHashKey key;			/* 条目的哈希键 - 必须是第一个 */
	Counters	counters;		/* 此查询的统计信息 */
	Size		query_offset;	/* 外部文件中的查询文本偏移量 */
	int			query_len;		/* 查询字符串中有效字节的数量，或 -1 */
	int			encoding;		/* 查询文本编码 */
	slock_t		mutex;			/* 仅保护计数器 */
} pgssEntry;

/*
 * 全局共享状态
 */
typedef struct pgssSharedState
{
	LWLock	   *lock;			/* 保护哈希表的搜索/修改 */
	double		cur_median_usage;	/* 哈希表中的当前中位数使用量 */
	Size		mean_query_len; /* 当前平均条目文本长度 */
	slock_t		mutex;			/* 仅保护以下字段： */
	Size		extent;			/* 查询文件的当前范围 */
	int			n_writers;		/* 查询文件的活动写入者数量 */
	int			gc_count;		/* 查询文件的垃圾收集周期计数 */
	pgssGlobalStats stats;		/* pgss 的全局统计信息 */
} pgssSharedState;

/*---- 本地变量 ----*/

/* ExecutorRun+ProcessUtility 调用的当前嵌套深度 */
static int	exec_nested_level = 0;

/* 计划器调用的当前嵌套深度 */
static int	plan_nested_level = 0;

/* 在卸载时保存的钩子值 */
static shmem_request_hook_type prev_shmem_request_hook = NULL;
static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;
static planner_hook_type prev_planner_hook = NULL;
static ExecutorStart_hook_type prev_ExecutorStart = NULL;
static ExecutorRun_hook_type prev_ExecutorRun = NULL;
static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
static ProcessUtility_hook_type prev_ProcessUtility = NULL;

/* 共享内存状态的链接 */
static pgssSharedState *pgss = NULL;
static HTAB *pgss_hash = NULL;

/*---- GUC 变量 ----*/

typedef enum
{
	PGSS_TRACK_NONE,			/* 跟踪无语句 */
	PGSS_TRACK_TOP,				/* 仅顶级语句 */
	PGSS_TRACK_ALL				/* 所有语句，包括嵌套的语句 */
}			PGSSTrackLevel;

static const struct config_enum_entry track_options[] =
{
	{"none", PGSS_TRACK_NONE, false},
	{"top", PGSS_TRACK_TOP, false},
	{"all", PGSS_TRACK_ALL, false},
	{NULL, 0, false}
};

static int	pgss_max;			/* 最大跟踪语句数 */
static int	pgss_track;			/* 跟踪级别 */
static bool pgss_track_utility; /* 是否跟踪实用命令 */
static bool pgss_track_planning;	/* 是否跟踪规划持续时间 */
static bool pgss_save;			/* 是否在关闭期间保存统计信息 */


#define pgss_enabled(level) \
	(!IsParallelWorker() && \
	(pgss_track == PGSS_TRACK_ALL || \
	(pgss_track == PGSS_TRACK_TOP && (level) == 0)))

#define record_gc_qtexts() \
	do { \
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss; \
		SpinLockAcquire(&fc_s->mutex); \
		fc_s->gc_count++; \
		SpinLockRelease(&fc_s->mutex); \
	} while(0)

/*---- 函数声明 ----*/

void		_PG_init(void);

PG_FUNCTION_INFO_V1(pg_stat_statements_reset);
PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_7);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_2);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_3);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_8);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_9);
PG_FUNCTION_INFO_V1(pg_stat_statements_1_10);
PG_FUNCTION_INFO_V1(pg_stat_statements);
PG_FUNCTION_INFO_V1(pg_stat_statements_info);

static void fc_pgss_shmem_request(void);
static void fc_pgss_shmem_startup(void);
static void fc_pgss_shmem_shutdown(int fc_code, Datum fc_arg);
static void fc_pgss_post_parse_analyze(ParseState *fc_pstate, Query *fc_query,
									JumbleState *fc_jstate);
static PlannedStmt *fc_pgss_planner(Query *fc_parse,
								 const char *fc_query_string,
								 int fc_cursorOptions,
								 ParamListInfo fc_boundParams);
static void fc_pgss_ExecutorStart(QueryDesc *fc_queryDesc, int fc_eflags);
static void fc_pgss_ExecutorRun(QueryDesc *fc_queryDesc,
							 ScanDirection fc_direction,
							 uint64 fc_count, bool fc_execute_once);
static void fc_pgss_ExecutorFinish(QueryDesc *fc_queryDesc);
static void fc_pgss_ExecutorEnd(QueryDesc *fc_queryDesc);
static void fc_pgss_ProcessUtility(PlannedStmt *fc_pstmt, const char *fc_queryString,
								bool fc_readOnlyTree,
								ProcessUtilityContext fc_context, ParamListInfo fc_params,
								QueryEnvironment *fc_queryEnv,
								DestReceiver *fc_dest, QueryCompletion *fc_qc);
static void fc_pgss_store(const char *fc_query, uint64 fc_queryId,
					   int fc_query_location, int fc_query_len,
					   pgssStoreKind fc_kind,
					   double fc_total_time, uint64 fc_rows,
					   const BufferUsage *fc_bufusage,
					   const WalUsage *fc_walusage,
					   const struct JitInstrumentation *fc_jitusage,
					   JumbleState *fc_jstate);
static void fc_pg_stat_statements_internal(FunctionCallInfo fc_fcinfo,
										pgssVersion fc_api_version,
										bool fc_showtext);
static Size fc_pgss_memsize(void);
static pgssEntry *fc_entry_alloc(pgssHashKey *fc_key, Size fc_query_offset, int fc_query_len,
							  int fc_encoding, bool fc_sticky);
static void fc_entry_dealloc(void);
static bool fc_qtext_store(const char *fc_query, int fc_query_len,
						Size *fc_query_offset, int *fc_gc_count);
static char *fc_qtext_load_file(Size *fc_buffer_size);
static char *fc_qtext_fetch(Size fc_query_offset, int fc_query_len,
						 char *fc_buffer, Size fc_buffer_size);
static bool fc_need_gc_qtexts(void);
static void fc_gc_qtexts(void);
static void fc_entry_reset(Oid fc_userid, Oid fc_dbid, uint64 fc_queryid);
static char *fc_generate_normalized_query(JumbleState *fc_jstate, const char *fc_query,
									   int fc_query_loc, int *fc_query_len_p);
static void fc_fill_in_constant_lengths(JumbleState *fc_jstate, const char *fc_query,
									 int fc_query_loc);
static int	fc_comp_location(const void *fc_a, const void *fc_b);


/*
 * 模块加载回调
 */
void _PG_init(void)
{
	/*
	 * 为了创建我们的共享内存区域，我们必须通过
	 * shared_preload_libraries 加载。如果没有，则退出而不挂钩到任何
	 * 主系统中。 （我们在这里不抛出错误，因为即使模块未激活，
	 * 允许创建 pg_stat_statements 函数似乎是有用的。然而，
	 * 这些函数必须保护自己以防被调用。）
	 */
	if (!process_shared_preload_libraries_in_progress)
		return;

	/*
	 * 通知主进程我们希望在 compute_query_id 设置为自动时启用 query_id 计算。
	 */
	EnableQueryId();

	/*
	 * 定义（或重新定义）自定义 GUC 变量。
	 */
	DefineCustomIntVariable("pg_stat_statements.max",
							"Sets the maximum number of statements tracked by pg_stat_statements.",
							NULL,
							&pgss_max,
							5000,
							100,
							INT_MAX / 2,
							PGC_POSTMASTER,
							0,
							NULL,
							NULL,
							NULL);

	DefineCustomEnumVariable("pg_stat_statements.track",
							 "Selects which statements are tracked by pg_stat_statements.",
							 NULL,
							 &pgss_track,
							 PGSS_TRACK_TOP,
							 track_options,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("pg_stat_statements.track_utility",
							 "Selects whether utility commands are tracked by pg_stat_statements.",
							 NULL,
							 &pgss_track_utility,
							 true,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("pg_stat_statements.track_planning",
							 "Selects whether planning duration is tracked by pg_stat_statements.",
							 NULL,
							 &pgss_track_planning,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("pg_stat_statements.save",
							 "Save pg_stat_statements statistics across server shutdowns.",
							 NULL,
							 &pgss_save,
							 true,
							 PGC_SIGHUP,
							 0,
							 NULL,
							 NULL,
							 NULL);

	MarkGUCPrefixReserved("pg_stat_statements");

	/*
	 * 安装钩子。
	 */
	prev_shmem_request_hook = shmem_request_hook;
	shmem_request_hook = fc_pgss_shmem_request;
	prev_shmem_startup_hook = shmem_startup_hook;
	shmem_startup_hook = fc_pgss_shmem_startup;
	prev_post_parse_analyze_hook = post_parse_analyze_hook;
	post_parse_analyze_hook = fc_pgss_post_parse_analyze;
	prev_planner_hook = planner_hook;
	planner_hook = fc_pgss_planner;
	prev_ExecutorStart = ExecutorStart_hook;
	ExecutorStart_hook = fc_pgss_ExecutorStart;
	prev_ExecutorRun = ExecutorRun_hook;
	ExecutorRun_hook = fc_pgss_ExecutorRun;
	prev_ExecutorFinish = ExecutorFinish_hook;
	ExecutorFinish_hook = fc_pgss_ExecutorFinish;
	prev_ExecutorEnd = ExecutorEnd_hook;
	ExecutorEnd_hook = fc_pgss_ExecutorEnd;
	prev_ProcessUtility = ProcessUtility_hook;
	ProcessUtility_hook = fc_pgss_ProcessUtility;
}

/*
 * shmem_request 钩子：请求额外的共享资源。我们将在 pgss_shmem_startup() 中分配或附加共享资源。
 */
static void fc_pgss_shmem_request(void)
{
	if (prev_shmem_request_hook)
		prev_shmem_request_hook();

	RequestAddinShmemSpace(fc_pgss_memsize());
	RequestNamedLWLockTranche("pg_stat_statements", 1);
}

/*
 * shmem_startup 钩子：分配或附加到共享内存，
 * 然后从文件中加载任何预先存在的统计信息。
 * 还创建并加载查询文本文件，预计在模块启用时
 * 该文件会存在（即使为空）。
 */
static void fc_pgss_shmem_startup(void)
{
	bool		fc_found;
	HASHCTL		fc_info;
	FILE	   *fc_file = NULL;
	FILE	   *fc_qfile = NULL;
	uint32		fc_header;
	int32		fc_num;
	int32		fc_pgver;
	int32		fc_i;
	int			fc_buffer_size;
	char	   *fc_buffer = NULL;

	if (prev_shmem_startup_hook)
		prev_shmem_startup_hook();

	/* 如果这是主进程内的重启，则重置 */
	pgss = NULL;
	pgss_hash = NULL;

	/*
	 * 创建或附加到共享内存状态，包括哈希表
	 */
	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);

	pgss = ShmemInitStruct("pg_stat_statements",
						   sizeof(pgssSharedState),
						   &fc_found);

	if (!fc_found)
	{
		/* 第一次通过 ... */
		pgss->lock = &(GetNamedLWLockTranche("pg_stat_statements"))->lock;
		pgss->cur_median_usage = ASSUMED_MEDIAN_INIT;
		pgss->mean_query_len = ASSUMED_LENGTH_INIT;
		SpinLockInit(&pgss->mutex);
		pgss->extent = 0;
		pgss->n_writers = 0;
		pgss->gc_count = 0;
		pgss->stats.dealloc = 0;
		pgss->stats.stats_reset = GetCurrentTimestamp();
	}

	fc_info.keysize = sizeof(pgssHashKey);
	fc_info.entrysize = sizeof(pgssEntry);
	pgss_hash = ShmemInitHash("pg_stat_statements hash",
							  pgss_max, pgss_max,
							  &fc_info,
							  HASH_ELEM | HASH_BLOBS);

	LWLockRelease(AddinShmemInitLock);

	/*
	 * 如果我们在postmaster（或独立后端...）中，设置一个shmem
	 * 退出钩子以将统计信息转储到磁盘。
	 */
	if (!IsUnderPostmaster)
		on_shmem_exit(fc_pgss_shmem_shutdown, (Datum) 0);

	/*
	 * 如果其他进程已经完成我们的初始化，则完成。
	 */
	if (fc_found)
		return;

	/*
	 * 注意：我们在这里不担心锁，因为在到达此代码时不应有其他
	 * 进程正在运行。
	 */

	/* 删除可能因崩溃而留下的查询文本文件 */
	unlink(PGSS_TEXT_FILE);

	/* 分配新的查询文本临时文件 */
	fc_qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
	if (fc_qfile == NULL)
		goto write_error;

	/*
	 * 如果我们被告知不加载旧统计信息，则完成。 （注意我们在此情况下不
	 * 尝试取消链接任何旧转储文件。这似乎有些可疑，但这是历史行为。）
	 */
	if (!pgss_save)
	{
		FreeFile(fc_qfile);
		return;
	}

	/*
	 * 尝试从转储文件加载旧统计信息。
	 */
	fc_file = AllocateFile(PGSS_DUMP_FILE, PG_BINARY_R);
	if (fc_file == NULL)
	{
		if (errno != ENOENT)
			goto read_error;
		/* 没有现有的持久化统计文件，因此我们完成了 */
		FreeFile(fc_qfile);
		return;
	}

	fc_buffer_size = 2048;
	fc_buffer = (char *) palloc(fc_buffer_size);

	if (fread(&fc_header, sizeof(uint32), 1, fc_file) != 1 ||
		fread(&fc_pgver, sizeof(uint32), 1, fc_file) != 1 ||
		fread(&fc_num, sizeof(int32), 1, fc_file) != 1)
		goto read_error;

	if (fc_header != PGSS_FILE_HEADER ||
		fc_pgver != PGSS_PG_MAJOR_VERSION)
		goto data_error;

	for (fc_i = 0; fc_i < fc_num; fc_i++)
	{
		pgssEntry	fc_temp;
		pgssEntry  *fc_entry;
		Size		fc_query_offset;

		if (fread(&fc_temp, sizeof(pgssEntry), 1, fc_file) != 1)
			goto read_error;

		/* 编码是我们可以轻松检查的唯一字段 */
		if (!PG_VALID_BE_ENCODING(fc_temp.encoding))
			goto data_error;

		/* 根据需要调整缓冲区大小 */
		if (fc_temp.query_len >= fc_buffer_size)
		{
			fc_buffer_size = Max(fc_buffer_size * 2, fc_temp.query_len + 1);
			fc_buffer = repalloc(fc_buffer, fc_buffer_size);
		}

		if (fread(fc_buffer, 1, fc_temp.query_len + 1, fc_file) != fc_temp.query_len + 1)
			goto read_error;

		/* 应该有一个尾随的空值，但让我们确保 */
		fc_buffer[fc_temp.query_len] = '\0';

		/* 跳过加载“粘性”条目 */
		if (IS_STICKY(fc_temp.counters))
			continue;

		/* 存储查询文本 */
		fc_query_offset = pgss->extent;
		if (fwrite(fc_buffer, 1, fc_temp.query_len + 1, fc_qfile) != fc_temp.query_len + 1)
			goto write_error;
		pgss->extent += fc_temp.query_len + 1;

		/* 创建哈希表条目（如果条目过多则丢弃旧条目） */
		fc_entry = fc_entry_alloc(&fc_temp.key, fc_query_offset, fc_temp.query_len,
							fc_temp.encoding,
							false);

		/* 复制实际统计数据 */
		fc_entry->counters = fc_temp.counters;
	}

	/* 读取pg_stat_statements的全局统计信息 */
	if (fread(&pgss->stats, sizeof(pgssGlobalStats), 1, fc_file) != 1)
		goto read_error;

	pfree(fc_buffer);
	FreeFile(fc_file);
	FreeFile(fc_qfile);

	/*
	 * 删除持久化的统计文件，以免包含在
	 * 备份/复制备用等中。将在下一个
	 * 关闭时写入新文件。
	 *
	 * 注意：如果PGSS_TEXT_FILE包含在基础备份中是可以的，
	 * 因为我们在启动时删除该文件；它的作用与
	 * PGSS_DUMP_FILE相反，它只是应该在
	 * 服务器运行时存在，而PGSS_DUMP_FILE只应在
	 * 服务器未运行时存在。保留该文件不会导致
	 * 新恢复的数据库有虚假的执行成本记录，
	 * 这是我们真正关注的问题。
	 */
	unlink(PGSS_DUMP_FILE);

	return;

read_error:
	ereport(LOG,
			(errcode_for_file_access(),
			 errmsg("could not read file \"%s\": %m",
					PGSS_DUMP_FILE)));
	goto fail;
data_error:
	ereport(LOG,
			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
			 errmsg("ignoring invalid data in file \"%s\"",
					PGSS_DUMP_FILE)));
	goto fail;
write_error:
	ereport(LOG,
			(errcode_for_file_access(),
			 errmsg("could not write file \"%s\": %m",
					PGSS_TEXT_FILE)));
fail:
	if (fc_buffer)
		pfree(fc_buffer);
	if (fc_file)
		FreeFile(fc_file);
	if (fc_qfile)
		FreeFile(fc_qfile);
	/* 如果可能，丢弃虚假的文件；忽略任何错误 */
	unlink(PGSS_DUMP_FILE);

	/*
	 * 在这里不要取消链接PGSS_TEXT_FILE；在启用pg_stat_statements
	 * 时，它应始终存在于服务器运行期间
	 */
}

/*
 * shmem_shutdown钩子：将统计信息转储到文件。
 *
 * 注意：我们不需要获取锁，因为在调用时不应有
 * 其他进程正在运行。
 */
static void fc_pgss_shmem_shutdown(int fc_code, Datum fc_arg)
{
	FILE	   *fc_file;
	char	   *fc_qbuffer = NULL;
	Size		fc_qbuffer_size = 0;
	HASH_SEQ_STATUS fc_hash_seq;
	int32		fc_num_entries;
	pgssEntry  *fc_entry;

	/* 在崩溃期间不要尝试转储。 */
	if (fc_code)
		return;

	/* 安全检查 ... 不应在这里，除非shmem已设置。 */
	if (!pgss || !pgss_hash)
		return;

	/* 如果被告知不转储，则不转储。 */
	if (!pgss_save)
		return;

	fc_file = AllocateFile(PGSS_DUMP_FILE ".tmp", PG_BINARY_W);
	if (fc_file == NULL)
		goto error;

	if (fwrite(&PGSS_FILE_HEADER, sizeof(uint32), 1, fc_file) != 1)
		goto error;
	if (fwrite(&PGSS_PG_MAJOR_VERSION, sizeof(uint32), 1, fc_file) != 1)
		goto error;
	fc_num_entries = hash_get_num_entries(pgss_hash);
	if (fwrite(&fc_num_entries, sizeof(int32), 1, fc_file) != 1)
		goto error;

	fc_qbuffer = fc_qtext_load_file(&fc_qbuffer_size);
	if (fc_qbuffer == NULL)
		goto error;

	/*
	 * 在序列化到磁盘时，我们立即在其
	 * 条目数据之后存储查询文本。由此排除任何孤立的查询文本。
	 */
	hash_seq_init(&fc_hash_seq, pgss_hash);
	while ((fc_entry = hash_seq_search(&fc_hash_seq)) != NULL)
	{
		int			fc_len = fc_entry->query_len;
		char	   *fc_qstr = fc_qtext_fetch(fc_entry->query_offset, fc_len,
									   fc_qbuffer, fc_qbuffer_size);

		if (fc_qstr == NULL)
			continue;			/* 忽略任何带有虚假文本的条目 */

		if (fwrite(fc_entry, sizeof(pgssEntry), 1, fc_file) != 1 ||
			fwrite(fc_qstr, 1, fc_len + 1, fc_file) != fc_len + 1)
		{
			/* 注意：我们假设hash_seq_term不会改变errno */
			hash_seq_term(&fc_hash_seq);
			goto error;
		}
	}

	/* 转储pg_stat_statements的全局统计信息 */
	if (fwrite(&pgss->stats, sizeof(pgssGlobalStats), 1, fc_file) != 1)
		goto error;

	free(fc_qbuffer);
	fc_qbuffer = NULL;

	if (FreeFile(fc_file))
	{
		fc_file = NULL;
		goto error;
	}

	/*
	 * 将文件重命名到目标位置，以便我们原子性地替换任何旧文件。
	 */
	(void) durable_rename(PGSS_DUMP_FILE ".tmp", PGSS_DUMP_FILE, LOG);

	/* 取消链接查询文本文件；在关闭期间不需要它 */
	unlink(PGSS_TEXT_FILE);

	return;

error:
	ereport(LOG,
			(errcode_for_file_access(),
			 errmsg("could not write file \"%s\": %m",
					PGSS_DUMP_FILE ".tmp")));
	if (fc_qbuffer)
		free(fc_qbuffer);
	if (fc_file)
		FreeFile(fc_file);
	unlink(PGSS_DUMP_FILE ".tmp");
	unlink(PGSS_TEXT_FILE);
}

/*
 * 解析后分析钩子：用 queryId 标记查询
 */
static void fc_pgss_post_parse_analyze(ParseState *fc_pstate, Query *fc_query, JumbleState *fc_jstate)
{
	if (prev_post_parse_analyze_hook)
		prev_post_parse_analyze_hook(fc_pstate, fc_query, fc_jstate);

	/* 安全检查... */
	if (!pgss || !pgss_hash || !pgss_enabled(exec_nested_level))
		return;

	/*
	 * 清除与准备语句相关的 utility 的 queryId，因为这些将
	 * 继承自底层语句的 queryId（除了 DEALLOCATE 完全不跟踪）。
	 */
	if (fc_query->utilityStmt)
	{
		if (pgss_track_utility && !PGSS_HANDLED_UTILITY(fc_query->utilityStmt))
			fc_query->queryId = UINT64CONST(0);
		return;
	}

	/*
	 * 如果查询打乱能够识别任何可忽略的常量，我们
	 * 立即为查询创建一个哈希表条目，以便记录查询字符串的标准化形式。
	 * 如果没有这样的常量，标准化字符串将与查询文本
	 * 一样，所以不需要提前条目。
	 */
	if (fc_jstate && fc_jstate->clocations_count > 0)
		fc_pgss_store(fc_pstate->p_sourcetext,
				   fc_query->queryId,
				   fc_query->stmt_location,
				   fc_query->stmt_len,
				   PGSS_INVALID,
				   0,
				   0,
				   NULL,
				   NULL,
				   NULL,
				   fc_jstate);
}

/*
 * 计划者钩子：转发到常规计划者，但在需要时测量计划时间。
 */
static PlannedStmt * fc_pgss_planner(Query *fc_parse,
			 const char *fc_query_string,
			 int fc_cursorOptions,
			 ParamListInfo fc_boundParams)
{
	PlannedStmt *fc_result;

	/*
	 * 如果没有提供 query_string，我们无法处理查询，因为
	 * pgss_store 需要它。我们也会忽略没有 queryid 的查询，因为它将
	 * 被视为一个 utility 语句，这可能并非如此。
	 *
	 * 请注意，planner_hook 可以从计划者本身被调用，因此我们
	 * 有一个特定的计划者嵌套级别。然而，包含可优化语句的 utility
	 * 命令也可以调用计划者，常规 DML 也是如此（例如对于底层外键查询）。
	 * 因此，仅测试计划者嵌套级别不足以检测真正的
	 * 顶级计划者调用。
	 */
	if (pgss_enabled(plan_nested_level + exec_nested_level)
		&& pgss_track_planning && fc_query_string
		&& fc_parse->queryId != UINT64CONST(0))
	{
		instr_time	fc_start;
		instr_time	fc_duration;
		BufferUsage fc_bufusage_start,
					fc_bufusage;
		WalUsage	fc_walusage_start,
					fc_walusage;

		/* 我们需要跟踪缓冲区使用情况，因为计划者可以访问它们。 */
		fc_bufusage_start = pgBufferUsage;

		/*
		 * 同样，计划者在某些情况下可以写一些 WAL 记录
		 * （例如，为那些被 WAL 记录的设置提示位）
		 */
		fc_walusage_start = pgWalUsage;
		INSTR_TIME_SET_CURRENT(fc_start);

		plan_nested_level++;
		PG_TRY();
		{
			if (prev_planner_hook)
				fc_result = prev_planner_hook(fc_parse, fc_query_string, fc_cursorOptions,
										   fc_boundParams);
			else
				fc_result = standard_planner(fc_parse, fc_query_string, fc_cursorOptions,
										  fc_boundParams);
		}
		PG_FINALLY();
		{
			plan_nested_level--;
		}
		PG_END_TRY();

		INSTR_TIME_SET_CURRENT(fc_duration);
		INSTR_TIME_SUBTRACT(fc_duration, fc_start);

		/* 计算缓冲计数器的差异。 */
		memset(&fc_bufusage, 0, sizeof(BufferUsage));
		BufferUsageAccumDiff(&fc_bufusage, &pgBufferUsage, &fc_bufusage_start);

		/* 计算 WAL 计数器的差异。 */
		memset(&fc_walusage, 0, sizeof(WalUsage));
		WalUsageAccumDiff(&fc_walusage, &pgWalUsage, &fc_walusage_start);

		fc_pgss_store(fc_query_string,
				   fc_parse->queryId,
				   fc_parse->stmt_location,
				   fc_parse->stmt_len,
				   PGSS_PLAN,
				   INSTR_TIME_GET_MILLISEC(fc_duration),
				   0,
				   &fc_bufusage,
				   &fc_walusage,
				   NULL,
				   NULL);
	}
	else
	{
		if (prev_planner_hook)
			fc_result = prev_planner_hook(fc_parse, fc_query_string, fc_cursorOptions,
									   fc_boundParams);
		else
			fc_result = standard_planner(fc_parse, fc_query_string, fc_cursorOptions,
									  fc_boundParams);
	}

	return fc_result;
}

/*
 * ExecutorStart 钩子：如有必要，启动跟踪
 */
static void fc_pgss_ExecutorStart(QueryDesc *fc_queryDesc, int fc_eflags)
{
	if (prev_ExecutorStart)
		prev_ExecutorStart(fc_queryDesc, fc_eflags);
	else
		standard_ExecutorStart(fc_queryDesc, fc_eflags);

	/*
	 * 如果查询的 queryId 为零，则不跟踪它。这可以防止优化
	 * 语句在 utility 语句中直接包含时被重复计数。
	 */
	if (pgss_enabled(exec_nested_level) && fc_queryDesc->plannedstmt->queryId != UINT64CONST(0))
	{
		/*
		 * 设置以跟踪 ExecutorRun 中的总耗时。确保
		 * 在每个查询上下文中分配空间，以便在
		 * ExecutorEnd 时释放。
		 */
		if (fc_queryDesc->totaltime == NULL)
		{
			MemoryContext fc_oldcxt;

			fc_oldcxt = MemoryContextSwitchTo(fc_queryDesc->estate->es_query_cxt);
			fc_queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL, false);
			MemoryContextSwitchTo(fc_oldcxt);
		}
	}
}

/*
 * ExecutorRun 钩子：我们需要做的就是跟踪嵌套深度
 */
static void fc_pgss_ExecutorRun(QueryDesc *fc_queryDesc, ScanDirection fc_direction, uint64 fc_count,
				 bool fc_execute_once)
{
	exec_nested_level++;
	PG_TRY();
	{
		if (prev_ExecutorRun)
			prev_ExecutorRun(fc_queryDesc, fc_direction, fc_count, fc_execute_once);
		else
			standard_ExecutorRun(fc_queryDesc, fc_direction, fc_count, fc_execute_once);
	}
	PG_FINALLY();
	{
		exec_nested_level--;
	}
	PG_END_TRY();
}

/*
 * ExecutorFinish 钩子：我们需要做的就是跟踪嵌套深度
 */
static void fc_pgss_ExecutorFinish(QueryDesc *fc_queryDesc)
{
	exec_nested_level++;
	PG_TRY();
	{
		if (prev_ExecutorFinish)
			prev_ExecutorFinish(fc_queryDesc);
		else
			standard_ExecutorFinish(fc_queryDesc);
	}
	PG_FINALLY();
	{
		exec_nested_level--;
	}
	PG_END_TRY();
}

/*
 * ExecutorEnd 钩子：如有必要，存储结果
 */
static void fc_pgss_ExecutorEnd(QueryDesc *fc_queryDesc)
{
	uint64		fc_queryId = fc_queryDesc->plannedstmt->queryId;

	if (fc_queryId != UINT64CONST(0) && fc_queryDesc->totaltime &&
		pgss_enabled(exec_nested_level))
	{
		/*
		 * 确保统计信息累计完成。（注意：如果多个
		 * 钩子都执行此操作也是可以的。）
		 */
		InstrEndLoop(fc_queryDesc->totaltime);

		fc_pgss_store(fc_queryDesc->sourceText,
				   fc_queryId,
				   fc_queryDesc->plannedstmt->stmt_location,
				   fc_queryDesc->plannedstmt->stmt_len,
				   PGSS_EXEC,
				   fc_queryDesc->totaltime->total * 1000.0,	/* 转换为毫秒 */
				   fc_queryDesc->estate->es_processed,
				   &fc_queryDesc->totaltime->bufusage,
				   &fc_queryDesc->totaltime->walusage,
				   fc_queryDesc->estate->es_jit ? &fc_queryDesc->estate->es_jit->instr : NULL,
				   NULL);
	}

	if (prev_ExecutorEnd)
		prev_ExecutorEnd(fc_queryDesc);
	else
		standard_ExecutorEnd(fc_queryDesc);
}

/*
 * ProcessUtility 钩子
 */
static void fc_pgss_ProcessUtility(PlannedStmt *fc_pstmt, const char *fc_queryString,
					bool fc_readOnlyTree,
					ProcessUtilityContext fc_context,
					ParamListInfo fc_params, QueryEnvironment *fc_queryEnv,
					DestReceiver *fc_dest, QueryCompletion *fc_qc)
{
	Node	   *fc_parsetree = fc_pstmt->utilityStmt;
	uint64		fc_saved_queryId = fc_pstmt->queryId;
	int			fc_saved_stmt_location = fc_pstmt->stmt_location;
	int			fc_saved_stmt_len = fc_pstmt->stmt_len;

	/*
	 * 强制实用语句获取 queryId 为零。即使在语句包含可以推导出 queryId 的可优化语句（如 EXPLAIN 或 DECLARE CURSOR）的情况下，我们也这么做。对于此类情况，运行时控制将首先经过 ProcessUtility，然后是执行器，我们不希望执行器钩子执行任何操作，因为我们已经在实用级别测量语句的成本。
	 *
	 * 请注意，只有在 pg_stat_statements 启用并配置为跟踪实用语句的情况下，才会进行此操作，用户配置了其他扩展仅处理实用语句的可能性微乎其微。
	 */
	if (pgss_enabled(exec_nested_level) && pgss_track_utility)
		fc_pstmt->queryId = UINT64CONST(0);

	/*
	 * 如果是 EXECUTE 语句，我们不跟踪它，也不增加嵌套级别。这样可以让循环的计费归于底层的 PREPARE（通过执行器钩子），这更有用。
	 *
	 * 我们也不跟踪 PREPARE 的执行。如果我们这样做，我们将得到一个 PREPARE 的哈希表条目（哈希由查询字符串计算得出），然后是一个不同的相同查询字符串的条目（哈希由查询树计算得出）用于累积随后的 EXECUTE 的成本。这将造成困惑，并且与其他规划时间根本不包括的情况不一致。
	 *
	 * 同样，我们不跟踪 DEALLOCATE 的执行。
	 */
	if (pgss_track_utility && pgss_enabled(exec_nested_level) &&
		PGSS_HANDLED_UTILITY(fc_parsetree))
	{
		instr_time	fc_start;
		instr_time	fc_duration;
		uint64		fc_rows;
		BufferUsage fc_bufusage_start,
					fc_bufusage;
		WalUsage	fc_walusage_start,
					fc_walusage;

		fc_bufusage_start = pgBufferUsage;
		fc_walusage_start = pgWalUsage;
		INSTR_TIME_SET_CURRENT(fc_start);

		exec_nested_level++;
		PG_TRY();
		{
			if (prev_ProcessUtility)
				prev_ProcessUtility(fc_pstmt, fc_queryString, fc_readOnlyTree,
									fc_context, fc_params, fc_queryEnv,
									fc_dest, fc_qc);
			else
				standard_ProcessUtility(fc_pstmt, fc_queryString, fc_readOnlyTree,
										fc_context, fc_params, fc_queryEnv,
										fc_dest, fc_qc);
		}
		PG_FINALLY();
		{
			exec_nested_level--;
		}
		PG_END_TRY();

		/*
		 * 注意：在这里下方不要再次访问 *pstmt 数据结构。如果它是 ROLLBACK 或类似的操作，该数据结构可能已被释放。我们必须将仍需的所有内容复制到局部变量中，这点我们已经做了。
		 *
		 * 同样的原因，我们不能冒险将 pstmt->queryId 恢复到其先前的值，那将是一个好主意。
		 */

		INSTR_TIME_SET_CURRENT(fc_duration);
		INSTR_TIME_SUBTRACT(fc_duration, fc_start);

		/*
		 * 跟踪由 COPY、FETCH、CREATE TABLE AS、CREATE MATERIALIZED
		 * VIEW、REFRESH MATERIALIZED VIEW 和 SELECT INTO 的实用语句检索或影响的总行数。
		 */
		fc_rows = (fc_qc && (fc_qc->commandTag == CMDTAG_COPY ||
					   fc_qc->commandTag == CMDTAG_FETCH ||
					   fc_qc->commandTag == CMDTAG_SELECT ||
					   fc_qc->commandTag == CMDTAG_REFRESH_MATERIALIZED_VIEW)) ?
			fc_qc->nprocessed : 0;

		/* 计算缓冲计数器的差异。 */
		memset(&fc_bufusage, 0, sizeof(BufferUsage));
		BufferUsageAccumDiff(&fc_bufusage, &pgBufferUsage, &fc_bufusage_start);

		/* 计算 WAL 计数器的差异。 */
		memset(&fc_walusage, 0, sizeof(WalUsage));
		WalUsageAccumDiff(&fc_walusage, &pgWalUsage, &fc_walusage_start);

		fc_pgss_store(fc_queryString,
				   fc_saved_queryId,
				   fc_saved_stmt_location,
				   fc_saved_stmt_len,
				   PGSS_EXEC,
				   INSTR_TIME_GET_MILLISEC(fc_duration),
				   fc_rows,
				   &fc_bufusage,
				   &fc_walusage,
				   NULL,
				   NULL);
	}
	else
	{
		if (prev_ProcessUtility)
			prev_ProcessUtility(fc_pstmt, fc_queryString, fc_readOnlyTree,
								fc_context, fc_params, fc_queryEnv,
								fc_dest, fc_qc);
		else
			standard_ProcessUtility(fc_pstmt, fc_queryString, fc_readOnlyTree,
									fc_context, fc_params, fc_queryEnv,
									fc_dest, fc_qc);
	}
}

/*
 * 为语句存储一些统计信息。
 *
 * 如果 jstate 不为 NULL，那么我们正在尝试为尚未有统计信息的条目创建一个条目；我们只想记录规范化的查询字符串。此情况下 total_time、rows、bufusage 和 walusage 将被忽略。
 *
 * 如果 kind 是 PGSS_PLAN 或 PGSS_EXEC，则其值用作 Counters 字段中数组的位置。
 */
static void fc_pgss_store(const char *fc_query, uint64 fc_queryId,
		   int fc_query_location, int fc_query_len,
		   pgssStoreKind fc_kind,
		   double fc_total_time, uint64 fc_rows,
		   const BufferUsage *fc_bufusage,
		   const WalUsage *fc_walusage,
		   const struct JitInstrumentation *fc_jitusage,
		   JumbleState *fc_jstate)
{
	pgssHashKey fc_key;
	pgssEntry  *fc_entry;
	char	   *fc_norm_query = NULL;
	int			fc_encoding = GetDatabaseEncoding();

	Assert(fc_query != NULL);

	/* 安全检查... */
	if (!pgss || !pgss_hash)
		return;

	/*
	 * 如果 compute_query_id 未启用并且没有其他模块计算查询标识符，则无事可做。
	 */
	if (fc_queryId == UINT64CONST(0))
		return;

	/*
	 * 如果查询是多语句源字符串的一部分，则将我们的注意力限制在字符串的相关部分，并在需要时更新查询位置和长度。
	 */
	fc_query = CleanQuerytext(fc_query, &fc_query_location, &fc_query_len);

	/* 为哈希表搜索设置键 */

	/* 清除填充 */
	memset(&fc_key, 0, sizeof(pgssHashKey));

	fc_key.userid = GetUserId();
	fc_key.dbid = MyDatabaseId;
	fc_key.queryid = fc_queryId;
	fc_key.toplevel = (exec_nested_level == 0);

	/* 带共享锁查找哈希表条目。 */
	LWLockAcquire(pgss->lock, LW_SHARED);

	fc_entry = (pgssEntry *) hash_search(pgss_hash, &fc_key, HASH_FIND, NULL);

	/* 创建新条目，如果不存在 */  
	if (!fc_entry)
	{
		Size		fc_query_offset;
		int			fc_gc_count;
		bool		fc_stored;
		bool		fc_do_gc;

		/*  
		 * 如果调用者要求，则创建一个新的、标准化的查询字符串。我们在执行此操作时不需要保持锁定状态。（注意：在任何情况下，在我们未持有下面的锁定的间隔中，有可能其他人创建了重复的哈希表条目。该情况由 entry_alloc 处理。）  
		 */  
		if (fc_jstate)
		{
			LWLockRelease(pgss->lock);
			fc_norm_query = fc_generate_normalized_query(fc_jstate, fc_query,
												   fc_query_location,
												   &fc_query_len);
			LWLockAcquire(pgss->lock, LW_SHARED);
		}

		/* 在仅持有共享锁的情况下将新查询文本附加到文件 */  
		fc_stored = fc_qtext_store(fc_norm_query ? fc_norm_query : fc_query, fc_query_len,
							 &fc_query_offset, &fc_gc_count);

		/*  
		 * 确定是否需要在仍然持有共享锁的情况下对外部查询文本进行垃圾收集。此微优化避免了在持有专属锁时耗时决定这一点。  
		 */  
		fc_do_gc = fc_need_gc_qtexts();

		/* 需要专属锁以创建新的哈希表条目 - 升级 */  
		LWLockRelease(pgss->lock);
		LWLockAcquire(pgss->lock, LW_EXCLUSIVE);

		/*  
		 * 在我们未持有锁的情况下可能发生了垃圾收集。如果这种情况发生，这是不太可能的，那么我们上面存储的查询文本将被垃圾收集，因此需要再次写入。这应该足够少，以至于在持有专属锁时这样做并不是性能问题。  
		 */  
		if (!fc_stored || pgss->gc_count != fc_gc_count)
			fc_stored = fc_qtext_store(fc_norm_query ? fc_norm_query : fc_query, fc_query_len,
								 &fc_query_offset, NULL);

		/* 如果我们写入文本文件失败，放弃 */  
		if (!fc_stored)
			goto done;

		/* 可以创建新的哈希表条目 */  
		fc_entry = fc_entry_alloc(&fc_key, fc_query_offset, fc_query_len, fc_encoding,
							fc_jstate != NULL);

		/* 如果需要，同时持有专属锁执行垃圾收集 */  
		if (fc_do_gc)
			fc_gc_qtexts();
	}

	/* 增加计数，除非 jstate 为 NULL */  
	if (!fc_jstate)
	{
		/*  
		 * 在更新计数器时获取自旋锁（请参见文件开头关于锁定规则的注释）  
		 */  
		volatile pgssEntry *fc_e = (volatile pgssEntry *) fc_entry;

		Assert(fc_kind == PGSS_PLAN || fc_kind == PGSS_EXEC);

		SpinLockAcquire(&fc_e->mutex);

		/* 如果之前是粘性的，则“解粘”条目 */  
		if (IS_STICKY(fc_e->counters))
			fc_e->counters.usage = USAGE_INIT;

		fc_e->counters.calls[fc_kind] += 1;
		fc_e->counters.total_time[fc_kind] += fc_total_time;

		if (fc_e->counters.calls[fc_kind] == 1)
		{
			fc_e->counters.min_time[fc_kind] = fc_total_time;
			fc_e->counters.max_time[fc_kind] = fc_total_time;
			fc_e->counters.mean_time[fc_kind] = fc_total_time;
		}
		else
		{
			/*  
			 * Welford 的计算方差的精确方法。请参见  
			 * <http://www.johndcook.com/blog/standard_deviation/>  
			 */  
			double		fc_old_mean = fc_e->counters.mean_time[fc_kind];

			fc_e->counters.mean_time[fc_kind] +=
				(fc_total_time - fc_old_mean) / fc_e->counters.calls[fc_kind];
			fc_e->counters.sum_var_time[fc_kind] +=
				(fc_total_time - fc_old_mean) * (fc_total_time - fc_e->counters.mean_time[fc_kind]);

			/* 计算最小和最大时间 */  
			if (fc_e->counters.min_time[fc_kind] > fc_total_time)
				fc_e->counters.min_time[fc_kind] = fc_total_time;
			if (fc_e->counters.max_time[fc_kind] < fc_total_time)
				fc_e->counters.max_time[fc_kind] = fc_total_time;
		}
		fc_e->counters.rows += fc_rows;
		fc_e->counters.shared_blks_hit += fc_bufusage->shared_blks_hit;
		fc_e->counters.shared_blks_read += fc_bufusage->shared_blks_read;
		fc_e->counters.shared_blks_dirtied += fc_bufusage->shared_blks_dirtied;
		fc_e->counters.shared_blks_written += fc_bufusage->shared_blks_written;
		fc_e->counters.local_blks_hit += fc_bufusage->local_blks_hit;
		fc_e->counters.local_blks_read += fc_bufusage->local_blks_read;
		fc_e->counters.local_blks_dirtied += fc_bufusage->local_blks_dirtied;
		fc_e->counters.local_blks_written += fc_bufusage->local_blks_written;
		fc_e->counters.temp_blks_read += fc_bufusage->temp_blks_read;
		fc_e->counters.temp_blks_written += fc_bufusage->temp_blks_written;
		fc_e->counters.blk_read_time += INSTR_TIME_GET_MILLISEC(fc_bufusage->blk_read_time);
		fc_e->counters.blk_write_time += INSTR_TIME_GET_MILLISEC(fc_bufusage->blk_write_time);
		fc_e->counters.temp_blk_read_time += INSTR_TIME_GET_MILLISEC(fc_bufusage->temp_blk_read_time);
		fc_e->counters.temp_blk_write_time += INSTR_TIME_GET_MILLISEC(fc_bufusage->temp_blk_write_time);
		fc_e->counters.usage += USAGE_EXEC(total_time);
		fc_e->counters.wal_records += fc_walusage->wal_records;
		fc_e->counters.wal_fpi += fc_walusage->wal_fpi;
		fc_e->counters.wal_bytes += fc_walusage->wal_bytes;
		if (fc_jitusage)
		{
			fc_e->counters.jit_functions += fc_jitusage->created_functions;
			fc_e->counters.jit_generation_time += INSTR_TIME_GET_MILLISEC(fc_jitusage->generation_counter);

			if (INSTR_TIME_GET_MILLISEC(fc_jitusage->inlining_counter))
				fc_e->counters.jit_inlining_count++;
			fc_e->counters.jit_inlining_time += INSTR_TIME_GET_MILLISEC(fc_jitusage->inlining_counter);

			if (INSTR_TIME_GET_MILLISEC(fc_jitusage->optimization_counter))
				fc_e->counters.jit_optimization_count++;
			fc_e->counters.jit_optimization_time += INSTR_TIME_GET_MILLISEC(fc_jitusage->optimization_counter);

			if (INSTR_TIME_GET_MILLISEC(fc_jitusage->emission_counter))
				fc_e->counters.jit_emission_count++;
			fc_e->counters.jit_emission_time += INSTR_TIME_GET_MILLISEC(fc_jitusage->emission_counter);
		}

		SpinLockRelease(&fc_e->mutex);
	}

done:
	LWLockRelease(pgss->lock);

	/* 我们将此清理推迟到不再持有锁时 */  
	if (fc_norm_query)
		pfree(fc_norm_query);
}

/*  
 * 重置与 userid、dbid 和 queryid 相对应的语句统计信息。  
 */  
Datum pg_stat_statements_reset_1_7(PG_FUNCTION_ARGS)
{
	Oid			fc_userid;
	Oid			fc_dbid;
	uint64		fc_queryid;

	fc_userid = PG_GETARG_OID(0);
	fc_dbid = PG_GETARG_OID(1);
	fc_queryid = (uint64) PG_GETARG_INT64(2);

	fc_entry_reset(fc_userid, fc_dbid, fc_queryid);

	PG_RETURN_VOID();
}

/*  
 * 重置语句统计信息。  
 */  
Datum pg_stat_statements_reset(PG_FUNCTION_ARGS)
{
	fc_entry_reset(0, 0, 0);

	PG_RETURN_VOID();
}

/* 各种 API 版本的输出参数（列）数量 */  
#define PG_STAT_STATEMENTS_COLS_V1_0	14
#define PG_STAT_STATEMENTS_COLS_V1_1	18
#define PG_STAT_STATEMENTS_COLS_V1_2	19
#define PG_STAT_STATEMENTS_COLS_V1_3	23
#define PG_STAT_STATEMENTS_COLS_V1_8	32
#define PG_STAT_STATEMENTS_COLS_V1_9	33
#define PG_STAT_STATEMENTS_COLS_V1_10	43
#define PG_STAT_STATEMENTS_COLS			43	/* 以上的最大值 */  

/*  
 * 检索语句统计信息。  
 *  
 * 此功能的 SQL API 已多次更改，并可能在将来再次更改。为了支持在旧 SQL 声明的函数中使用新版本的可加载模块的情况，我们继续支持旧的 API 版本。对于 1.2 及更高版本，预期的 API 版本通过嵌入它到函数的 C 名称中来识别。不幸的是，我们在 1.1 时并没有聪明到做到这一点。  
 */  
Datum pg_stat_statements_1_10(PG_FUNCTION_ARGS)
{
	bool		fc_showtext = PG_GETARG_BOOL(0);

	fc_pg_stat_statements_internal(fcinfo, PGSS_V1_10, fc_showtext);

	return (Datum) 0;
}

Datum pg_stat_statements_1_9(PG_FUNCTION_ARGS)
{
	bool		fc_showtext = PG_GETARG_BOOL(0);

	fc_pg_stat_statements_internal(fcinfo, PGSS_V1_9, fc_showtext);

	return (Datum) 0;
}

Datum pg_stat_statements_1_8(PG_FUNCTION_ARGS)
{
	bool		fc_showtext = PG_GETARG_BOOL(0);

	fc_pg_stat_statements_internal(fcinfo, PGSS_V1_8, fc_showtext);

	return (Datum) 0;
}

Datum pg_stat_statements_1_3(PG_FUNCTION_ARGS)
{
	bool		fc_showtext = PG_GETARG_BOOL(0);

	fc_pg_stat_statements_internal(fcinfo, PGSS_V1_3, fc_showtext);

	return (Datum) 0;
}

Datum pg_stat_statements_1_2(PG_FUNCTION_ARGS)
{
	bool		fc_showtext = PG_GETARG_BOOL(0);

	fc_pg_stat_statements_internal(fcinfo, PGSS_V1_2, fc_showtext);

	return (Datum) 0;
}

/*  
 * pg_stat_statements() API 版本 1.0 和 1.1 的遗留入口点。  
 * 这可能会在某一天被移除。  
 */  
Datum pg_stat_statements(PG_FUNCTION_ARGS)
{
	/* 如果它真的属于 API 1.1，我们将在下面找出这一点 */  
	fc_pg_stat_statements_internal(fcinfo, PGSS_V1_0, true);

	return (Datum) 0;
}

/* pg_stat_statements() 的所有版本的公共代码 */  
static void fc_pg_stat_statements_internal(FunctionCallInfo fc_fcinfo,
							pgssVersion fc_api_version,
							bool fc_showtext)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fc_fcinfo->resultinfo;
	Oid			fc_userid = GetUserId();
	bool		fc_is_allowed_role = false;
	char	   *fc_qbuffer = NULL;
	Size		fc_qbuffer_size = 0;
	Size		fc_extent = 0;
	int			fc_gc_count = 0;
	HASH_SEQ_STATUS fc_hash_seq;
	pgssEntry  *fc_entry;

	/*  
	 * 超级用户或拥有 pg_read_all_stats 成员权限的角色是被允许的  
	 */  
	fc_is_allowed_role = has_privs_of_role(fc_userid, ROLE_PG_READ_ALL_STATS);

	/* 哈希表必须已经存在 */  
	if (!pgss || !pgss_hash)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("pg_stat_statements must be loaded via shared_preload_libraries")));

	InitMaterializedSRF(fc_fcinfo, 0);

	/*
	 * 检查我们是否拥有预期的输出参数数量。 除了
	 * 作为良好的安全检查外，我们在这里需要一个临时方案来检测API版本
	 * 1.1，该版本以不明智的方式嵌入到代码中。
	 */
	switch (fc_rsinfo->setDesc->natts)
	{
		case PG_STAT_STATEMENTS_COLS_V1_0:
			if (fc_api_version != PGSS_V1_0)
				elog(ERROR, "incorrect number of output arguments");
			break;
		case PG_STAT_STATEMENTS_COLS_V1_1:
			/* pg_stat_statements() 应该告诉我们 1.0 */
			if (fc_api_version != PGSS_V1_0)
				elog(ERROR, "incorrect number of output arguments");
			fc_api_version = PGSS_V1_1;
			break;
		case PG_STAT_STATEMENTS_COLS_V1_2:
			if (fc_api_version != PGSS_V1_2)
				elog(ERROR, "incorrect number of output arguments");
			break;
		case PG_STAT_STATEMENTS_COLS_V1_3:
			if (fc_api_version != PGSS_V1_3)
				elog(ERROR, "incorrect number of output arguments");
			break;
		case PG_STAT_STATEMENTS_COLS_V1_8:
			if (fc_api_version != PGSS_V1_8)
				elog(ERROR, "incorrect number of output arguments");
			break;
		case PG_STAT_STATEMENTS_COLS_V1_9:
			if (fc_api_version != PGSS_V1_9)
				elog(ERROR, "incorrect number of output arguments");
			break;
		case PG_STAT_STATEMENTS_COLS_V1_10:
			if (fc_api_version != PGSS_V1_10)
				elog(ERROR, "incorrect number of output arguments");
			break;
		default:
			elog(ERROR, "incorrect number of output arguments");
	}

	/*
	 * 我们希望在不持有任何
	 * pgss->lock 锁的情况下加载查询文本文件（如果需要的话）。 在最坏的情况下，我们将不得不在获得锁之后再次执行此操作，但这种情况不太可能，因此尽管偶尔会重复工作，但仍然值得。 如果在此时到我们获得共享锁之间，有人对文件进行写入（无论是零售 qtext_store() 还是垃圾回收），我们需要重新加载。 如果在我们查看时，qtext_store 实际上正在进行，我们不妨完全跳过这个推测性加载。
	 */
	if (fc_showtext)
	{
		int			fc_n_writers;

		/* 获取互斥锁以便我们可以检查变量 */
		{
			volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;

			SpinLockAcquire(&fc_s->mutex);
			fc_extent = fc_s->extent;
			fc_n_writers = fc_s->n_writers;
			fc_gc_count = fc_s->gc_count;
			SpinLockRelease(&fc_s->mutex);
		}

		/* 如果有活动写入者，现在加载文件没有意义 */
		if (fc_n_writers == 0)
			fc_qbuffer = fc_qtext_load_file(&fc_qbuffer_size);
	}

	/*
	 * 获取共享锁，如果必须的话，加载或重新加载查询文本文件，并
	 * 迭代哈希表条目。
	 *
	 * 对于大型哈希表，我们可能会持有锁更长的时间
	 * 这可能让人不悦。 然而，这只会阻止创建新的哈希
	 * 表条目，而且当哈希表越大，这种需求出现的可能性就越小。 所以我们可以希望这样是可以的。或许有一天我们会决定
	 * 我们需要对哈希表进行分区，以限制持有任何
	 * 一个锁的时间。
	 */
	LWLockAcquire(pgss->lock, LW_SHARED);

	if (fc_showtext)
	{
		/*
		 * 在这里可以安全地检查 extent 和 gc_count，而无需获取
		 * 互斥锁。 请注意，尽管其他进程可能在我们查看之后改变
		 * pgss->extent，但它们随后写入文件的字符串尚未
		 * 在哈希表中引用，因此我们不在乎我们是否看到它们。
		 *
		 * 如果 qtext_load_file 失败，我们只需继续；我们将为
		 * 每个查询文本返回 NULL。
		 */
		if (fc_qbuffer == NULL ||
			pgss->extent != fc_extent ||
			pgss->gc_count != fc_gc_count)
		{
			if (fc_qbuffer)
				free(fc_qbuffer);
			fc_qbuffer = fc_qtext_load_file(&fc_qbuffer_size);
		}
	}

	hash_seq_init(&fc_hash_seq, pgss_hash);
	while ((fc_entry = hash_seq_search(&fc_hash_seq)) != NULL)
	{
		Datum		fc_values[PG_STAT_STATEMENTS_COLS];
		bool		fc_nulls[PG_STAT_STATEMENTS_COLS];
		int			fc_i = 0;
		Counters	fc_tmp;
		double		fc_stddev;
		int64		fc_queryid = fc_entry->key.queryid;

		memset(fc_values, 0, sizeof(fc_values));
		memset(fc_nulls, 0, sizeof(fc_nulls));

		fc_values[fc_i++] = ObjectIdGetDatum(fc_entry->key.userid);
		fc_values[fc_i++] = ObjectIdGetDatum(fc_entry->key.dbid);
		if (fc_api_version >= PGSS_V1_9)
			fc_values[fc_i++] = BoolGetDatum(fc_entry->key.toplevel);

		if (fc_is_allowed_role || fc_entry->key.userid == fc_userid)
		{
			if (fc_api_version >= PGSS_V1_2)
				fc_values[fc_i++] = Int64GetDatumFast(fc_queryid);

			if (fc_showtext)
			{
				char	   *fc_qstr = fc_qtext_fetch(fc_entry->query_offset,
											   fc_entry->query_len,
											   fc_qbuffer,
											   fc_qbuffer_size);

				if (fc_qstr)
				{
					char	   *fc_enc;

					fc_enc = pg_any_to_server(fc_qstr,
										   fc_entry->query_len,
										   fc_entry->encoding);

					fc_values[fc_i++] = CStringGetTextDatum(fc_enc);

					if (fc_enc != fc_qstr)
						pfree(fc_enc);
				}
				else
				{
					/* 如果我们无法找到文本，则返回 null */
					fc_nulls[fc_i++] = true;
				}
			}
			else
			{
				/* 查询文本未请求 */
				fc_nulls[fc_i++] = true;
			}
		}
		else
		{
			/* 不显示 queryid */
			if (fc_api_version >= PGSS_V1_2)
				fc_nulls[fc_i++] = true;

			/*
			 * 不显示查询文本，但如果请求了查询文本，
			 * 则提示不显示的原因
			 */
			if (fc_showtext)
				fc_values[fc_i++] = CStringGetTextDatum("<insufficient privilege>");
			else
				fc_nulls[fc_i++] = true;
		}

		/* 将计数器复制到局部变量以保持锁定时间短 */
		{
			volatile pgssEntry *fc_e = (volatile pgssEntry *) fc_entry;

			SpinLockAcquire(&fc_e->mutex);
			fc_tmp = fc_e->counters;
			SpinLockRelease(&fc_e->mutex);
		}

		/* 如果未执行则跳过条目（即，它是一个待处理的“粘性”条目） */
		if (IS_STICKY(fc_tmp))
			continue;

		/* 注意我们依赖于 PGSS_PLAN 为 0 和 PGSS_EXEC 为 1。 */
		for (int fc_kind = 0; fc_kind < PGSS_NUMKIND; fc_kind++)
		{
			if (fc_kind == PGSS_EXEC || fc_api_version >= PGSS_V1_8)
			{
				fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.calls[fc_kind]);
				fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.total_time[fc_kind]);
			}

			if ((fc_kind == PGSS_EXEC && fc_api_version >= PGSS_V1_3) ||
				fc_api_version >= PGSS_V1_8)
			{
				fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.min_time[fc_kind]);
				fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.max_time[fc_kind]);
				fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.mean_time[fc_kind]);

				/*
				 * 注意我们在这里计算的是总体方差，而不是
				 * 样本方差，因为我们有整个
				 * 总体的数据，因此不使用贝塞尔校正，并且我们
				 * 不除以 tmp.calls - 1。
				 */
				if (fc_tmp.calls[fc_kind] > 1)
					fc_stddev = sqrt(fc_tmp.sum_var_time[fc_kind] / fc_tmp.calls[fc_kind]);
				else
					fc_stddev = 0.0;
				fc_values[fc_i++] = Float8GetDatumFast(fc_stddev);
			}
		}
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.rows);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.shared_blks_hit);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.shared_blks_read);
		if (fc_api_version >= PGSS_V1_1)
			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.shared_blks_dirtied);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.shared_blks_written);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.local_blks_hit);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.local_blks_read);
		if (fc_api_version >= PGSS_V1_1)
			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.local_blks_dirtied);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.local_blks_written);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.temp_blks_read);
		fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.temp_blks_written);
		if (fc_api_version >= PGSS_V1_1)
		{
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.blk_read_time);
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.blk_write_time);
		}
		if (fc_api_version >= PGSS_V1_10)
		{
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.temp_blk_read_time);
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.temp_blk_write_time);
		}
		if (fc_api_version >= PGSS_V1_8)
		{
			char		fc_buf[256];
			Datum		fc_wal_bytes;

			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.wal_records);
			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.wal_fpi);

			snprintf(fc_buf, sizeof fc_buf, UINT64_FORMAT, fc_tmp.wal_bytes);

			/* 转换为数字。 */
			fc_wal_bytes = DirectFunctionCall3(numeric_in,
											CStringGetDatum(fc_buf),
											ObjectIdGetDatum(0),
											Int32GetDatum(-1));
			fc_values[fc_i++] = fc_wal_bytes;
		}
		if (fc_api_version >= PGSS_V1_10)
		{
			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.jit_functions);
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.jit_generation_time);
			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.jit_inlining_count);
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.jit_inlining_time);
			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.jit_optimization_count);
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.jit_optimization_time);
			fc_values[fc_i++] = Int64GetDatumFast(fc_tmp.jit_emission_count);
			fc_values[fc_i++] = Float8GetDatumFast(fc_tmp.jit_emission_time);
		}

		Assert(fc_i == (fc_api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 :
					 fc_api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 :
					 fc_api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 :
					 fc_api_version == PGSS_V1_3 ? PG_STAT_STATEMENTS_COLS_V1_3 :
					 fc_api_version == PGSS_V1_8 ? PG_STAT_STATEMENTS_COLS_V1_8 :
					 fc_api_version == PGSS_V1_9 ? PG_STAT_STATEMENTS_COLS_V1_9 :
					 fc_api_version == PGSS_V1_10 ? PG_STAT_STATEMENTS_COLS_V1_10 :
					 -1 /* 如果忘记更新此断言，则失败 */ ));

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);
	}

	LWLockRelease(pgss->lock);

	if (fc_qbuffer)
		free(fc_qbuffer);
}

/* pg_stat_statements_info 的输出参数（列）数量 */
#define PG_STAT_STATEMENTS_INFO_COLS	2

/*
 * 返回 pg_stat_statements 的统计信息。
 */
Datum pg_stat_statements_info(PG_FUNCTION_ARGS)
{
	pgssGlobalStats fc_stats;
	TupleDesc	fc_tupdesc;
	Datum		fc_values[PG_STAT_STATEMENTS_INFO_COLS];
	bool		fc_nulls[PG_STAT_STATEMENTS_INFO_COLS];

	if (!pgss || !pgss_hash)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("pg_stat_statements must be loaded via shared_preload_libraries")));

	/* 为我们的结果类型构建一个元组描述符 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	MemSet(fc_values, 0, sizeof(fc_values));
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	/* 读取pg_stat_statements的全局统计信息 */
	{
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;

		SpinLockAcquire(&fc_s->mutex);
		fc_stats = fc_s->stats;
		SpinLockRelease(&fc_s->mutex);
	}

	fc_values[0] = Int64GetDatum(fc_stats.dealloc);
	fc_values[1] = TimestampTzGetDatum(fc_stats.stats_reset);

	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(fc_tupdesc, fc_values, fc_nulls)));
}

/*
 * 评估所需的共享内存空间。
 */
static Size fc_pgss_memsize(void)
{
	Size		fc_size;

	fc_size = MAXALIGN(sizeof(pgssSharedState));
	fc_size = add_size(fc_size, hash_estimate_size(pgss_max, sizeof(pgssEntry)));

	return fc_size;
}

/*
 * 分配一个新的哈希表条目。
 * 调用者必须对 pgss->lock 持有独占锁
 *
 * “query” 不需要以空字符结束；我们依赖于 query_len
 *
 * 如果“sticky”为真，则使新的条目人为地保持粘性，以便在查询执行完成时它很可能仍然存在。我们通过给它一个中位使用值而不是正常值来实现这一点。（严格来说，查询字符串是基于最佳努力进行标准化的，尽管在人工条件下很难证明这一点。）
 *
 * 注意：尽管需要独占锁，但目标条目已经存在并不是错误。这是因为 pgss_store 在未找到匹配项后会释放并重新获取锁；因此在我们等待获取独占锁的过程中，可能已经有其他人创建了该条目。
 */
static pgssEntry *
fc_entry_alloc(pgssHashKey *fc_key, Size fc_query_offset, int fc_query_len, int fc_encoding,
			bool fc_sticky)
{
	pgssEntry  *fc_entry;
	bool		fc_found;

	/* 如有需要，腾出空间 */
	while (hash_get_num_entries(pgss_hash) >= pgss_max)
		fc_entry_dealloc();

	/* 查找或创建具有所需哈希码的条目 */
	fc_entry = (pgssEntry *) hash_search(pgss_hash, fc_key, HASH_ENTER, &fc_found);

	if (!fc_found)
	{
		/* 新条目，初始化它 */

		/* 重置统计数据 */
		memset(&fc_entry->counters, 0, sizeof(Counters));
		/* 设置适当的初始使用计数 */
		fc_entry->counters.usage = fc_sticky ? pgss->cur_median_usage : USAGE_INIT;
		/* 每次重新初始化互斥体……我们假设没有人使用它 */
		SpinLockInit(&fc_entry->mutex);
		/* ……不要忘记查询文本元数据 */
		Assert(fc_query_len >= 0);
		fc_entry->query_offset = fc_query_offset;
		fc_entry->query_len = fc_query_len;
		fc_entry->encoding = fc_encoding;
	}

	return fc_entry;
}

/*
 * 用于按使用量升序排序的 qsort 比较器
 */
static int fc_entry_cmp(const void *fc_lhs, const void *fc_rhs)
{
	double		fc_l_usage = (*(pgssEntry *const *) fc_lhs)->counters.usage;
	double		fc_r_usage = (*(pgssEntry *const *) fc_rhs)->counters.usage;

	if (fc_l_usage < fc_r_usage)
		return -1;
	else if (fc_l_usage > fc_r_usage)
		return +1;
	else
		return 0;
}

/*
 * 释放使用最少的条目。
 *
 * 调用者必须对 pgss->lock 持有独占锁。
 */
static void fc_entry_dealloc(void)
{
	HASH_SEQ_STATUS fc_hash_seq;
	pgssEntry **fc_entries;
	pgssEntry  *fc_entry;
	int			fc_nvictims;
	int			fc_i;
	Size		fc_tottextlen;
	int			fc_nvalidtexts;

	/*
	 * 按使用量对条目进行排序，并释放 USAGE_DEALLOC_PERCENT 的条目。
	 * 在扫描表格时，将衰减因子应用于使用值，并更新平均查询长度。
	 *
	 * 请注意，平均查询长度几乎立即变得过时，因为我们在丢弃使用最少的条目之前计算它，而不是之后。希望这不会对平均值产生太大影响；似乎不值得进行两次遍历以获得更当前的结果。同样，新的 cur_median_usage 包括我们即将删除的条目。
	 */

	fc_entries = palloc(hash_get_num_entries(pgss_hash) * sizeof(pgssEntry *));

	fc_i = 0;
	fc_tottextlen = 0;
	fc_nvalidtexts = 0;

	hash_seq_init(&fc_hash_seq, pgss_hash);
	while ((fc_entry = hash_seq_search(&fc_hash_seq)) != NULL)
	{
		fc_entries[fc_i++] = fc_entry;
		/* “粘性”条目具有不同的使用衰减率。 */
		if (IS_STICKY(fc_entry->counters))
			fc_entry->counters.usage *= STICKY_DECREASE_FACTOR;
		else
			fc_entry->counters.usage *= USAGE_DECREASE_FACTOR;
		/* 在平均长度计算中，忽略已删除的文本。 */
		if (fc_entry->query_len >= 0)
		{
			fc_tottextlen += fc_entry->query_len + 1;
			fc_nvalidtexts++;
		}
	}

	/* 按使用量升序排序 */
	qsort(fc_entries, fc_i, sizeof(pgssEntry *), fc_entry_cmp);

	/* 记录（估算的）中位使用量 */
	if (fc_i > 0)
		pgss->cur_median_usage = fc_entries[fc_i / 2]->counters.usage;
	/* 记录平均查询长度 */
	if (fc_nvalidtexts > 0)
		pgss->mean_query_len = fc_tottextlen / fc_nvalidtexts;
	else
		pgss->mean_query_len = ASSUMED_LENGTH_INIT;

	/* 现在删除适当比例的最低使用条目 */
	fc_nvictims = Max(10, fc_i * USAGE_DEALLOC_PERCENT / 100);
	fc_nvictims = Min(fc_nvictims, fc_i);

	for (fc_i = 0; fc_i < fc_nvictims; fc_i++)
	{
		hash_search(pgss_hash, &fc_entries[fc_i]->key, HASH_REMOVE, NULL);
	}

	pfree(fc_entries);

	/* 增加释放条目的次数 */
	{
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;

		SpinLockAcquire(&fc_s->mutex);
		fc_s->stats.dealloc += 1;
		SpinLockRelease(&fc_s->mutex);
	}
}

/*
 * 给定一个查询字符串（不一定以空字符结束），在外部查询文本文件中分配一个新条目并存储该字符串。
 *
 * 如果成功，则返回true，并将新条目的偏移量存储到*query_offset中。如果gc_count不是NULL，则*gc_count被设置为到目前为止发生的垃圾回收次数。
 *
 * 失败时返回false。
 *
 * 调用者必须至少持有pgss->lock的共享锁，以防止并发垃圾回收。 持有共享锁的调用者应传递gc_count指针以获取垃圾回收的次数，以便他们在获得排他锁后重新检查计数，以检测是否发生垃圾回收（并移除了该条目）。
 */
static bool fc_qtext_store(const char *fc_query, int fc_query_len,
			Size *fc_query_offset, int *fc_gc_count)
{
	Size		fc_off;
	int			fc_fd;

	/*
	 * 我们使用自旋锁来保护extent/n_writers/gc_count，以便多个进程可以并发执行此函数。
	 */
	{
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;

		SpinLockAcquire(&fc_s->mutex);
		fc_off = fc_s->extent;
		fc_s->extent += fc_query_len + 1;
		fc_s->n_writers++;
		if (fc_gc_count)
			*fc_gc_count = fc_s->gc_count;
		SpinLockRelease(&fc_s->mutex);
	}

	*fc_query_offset = fc_off;

	/*
	 * 不允许文件增长超过qtext_load_file可以（理论上）处理的大小。 在32位平台上已经观察到可以达到这个限制。
	 */
	if (unlikely(fc_query_len >= MaxAllocHugeSize - fc_off))
	{
		errno = EFBIG;			/* 不完全正确，但可以使用 */
		fc_fd = -1;
		goto error;
	}

	/* 现在将数据写入成功保留的文件部分 */
	fc_fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY);
	if (fc_fd < 0)
		goto error;

	if (pg_pwrite(fc_fd, fc_query, fc_query_len, fc_off) != fc_query_len)
		goto error;
	if (pg_pwrite(fc_fd, "\0", 1, fc_off + fc_query_len) != 1)
		goto error;

	CloseTransientFile(fc_fd);

	/* 标记我们的写入完成 */
	{
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;

		SpinLockAcquire(&fc_s->mutex);
		fc_s->n_writers--;
		SpinLockRelease(&fc_s->mutex);
	}

	return true;

error:
	ereport(LOG,
			(errcode_for_file_access(),
			 errmsg("could not write file \"%s\": %m",
					PGSS_TEXT_FILE)));

	if (fc_fd >= 0)
		CloseTransientFile(fc_fd);

	/* 标记我们的写入完成 */
	{
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;

		SpinLockAcquire(&fc_s->mutex);
		fc_s->n_writers--;
		SpinLockRelease(&fc_s->mutex);
	}

	return false;
}

/*
 * 将外部查询文本文件读取到malloc'd缓冲区中。
 *
 * 如果无法读取，则返回NULL（不抛出错误），例如文件不存在或内存不足。
 *
 * 成功时，缓冲区大小也将返回到*buffer_size中。
 *
 * 这可以在pgss->lock上不加锁的情况下调用，但在这种情况下，调用者负责验证结果是否合理。
 */
static char * fc_qtext_load_file(Size *fc_buffer_size)
{
	char	   *fc_buf;
	int			fc_fd;
	struct stat fc_stat;
	Size		fc_nread;

	fc_fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY);
	if (fc_fd < 0)
	{
		if (errno != ENOENT)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not read file \"%s\": %m",
							PGSS_TEXT_FILE)));
		return NULL;
	}

	/* 获取文件长度 */
	if (fstat(fc_fd, &fc_stat))
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not stat file \"%s\": %m",
						PGSS_TEXT_FILE)));
		CloseTransientFile(fc_fd);
		return NULL;
	}

	/* 分配缓冲区；注意off_t可能比size_t宽 */
	if (fc_stat.st_size <= MaxAllocHugeSize)
		fc_buf = (char *) malloc(fc_stat.st_size);
	else
		fc_buf = NULL;
	if (fc_buf == NULL)
	{
		ereport(LOG,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Could not allocate enough memory to read file \"%s\".",
						   PGSS_TEXT_FILE)));
		CloseTransientFile(fc_fd);
		return NULL;
	}

	/*
	 * 好吧，读取文件。 Windows在我们尝试一次读取超过
	 * INT_MAX字节时会失败，其他平台可能也不喜欢这样，
	 * 因此将非常大的文件分为1GB的块读取。
	 */
	fc_nread = 0;
	while (fc_nread < fc_stat.st_size)
	{
		int			fc_toread = Min(1024 * 1024 * 1024, fc_stat.st_size - fc_nread);

		/*
		 * 如果我们得到短读并且errno没有被设置，原因可能是
		 * 垃圾回收在我们执行fstat()后截断了文件，因此我们不记录抱怨——
		 * 但我们也不返回数据，因为这很可能由于并发
		 * 来自垃圾回收的写入而损坏。
		 */
		errno = 0;
		if (read(fc_fd, fc_buf + fc_nread, fc_toread) != fc_toread)
		{
			if (errno)
				ereport(LOG,
						(errcode_for_file_access(),
						 errmsg("could not read file \"%s\": %m",
								PGSS_TEXT_FILE)));
			free(fc_buf);
			CloseTransientFile(fc_fd);
			return NULL;
		}
		fc_nread += fc_toread;
	}

	if (CloseTransientFile(fc_fd) != 0)
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", PGSS_TEXT_FILE)));

	*fc_buffer_size = fc_nread;
	return fc_buf;
}

/*
 * 在之前由qtext_load_file()读取的文件映像中定位查询文本。
 *
 * 我们验证给定的偏移量/长度，如果是无效的则返回NULL。否则，
 * 结果指向缓冲区内的一个以空字符结尾的字符串。
 */
static char * fc_qtext_fetch(Size fc_query_offset, int fc_query_len,
			char *fc_buffer, Size fc_buffer_size)
{
	/* 文件读取失败？ */
	if (fc_buffer == NULL)
		return NULL;
	/* 无效的偏移量/长度？ */
	if (fc_query_len < 0 ||
		fc_query_offset + fc_query_len >= fc_buffer_size)
		return NULL;
	/* 作为进一步的合理性检查，确保有一个结尾的空字符 */
	if (fc_buffer[fc_query_offset + fc_query_len] != '\0')
		return NULL;
	/* 看起来正常 */
	return fc_buffer + fc_query_offset;
}

/*
 * 我们需要对外部查询文本文件进行垃圾回收吗？
 *
 * 调用者应至少持有pgss->lock的共享锁。
 */
static bool fc_need_gc_qtexts(void)
{
	Size		fc_extent;

	/* 读取共享区段指针 */
	{
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;

		SpinLockAcquire(&fc_s->mutex);
		fc_extent = fc_s->extent;
		SpinLockRelease(&fc_s->mutex);
	}

	/*
	 * 如果文件大小不超过每个可能条目的 512 字节，则不要继续。
	 *
	 * 在这里和下一个测试中，如果 pgss_max 和/或 mean_query_len 较大，则 32 位机器存在溢出风险。强制进行无符号 64 位算术运算
	 * 以避免麻烦。
	 */
	if ((uint64) fc_extent < (uint64) 512 * pgss_max)
		return false;

	/*
	 * 如果文件膨胀率小于约 50%，则不要继续。在发生异常大的查询文本导致文件大小异常大的情况下，无法或不应采取任何措施。
	 * 我们努力维护平均查询长度，以防止垃圾回收不必要地影响性能。
	 */
	if ((uint64) fc_extent < (uint64) pgss->mean_query_len * pgss_max * 2)
		return false;

	return true;
}

/*
 * 在外部文件中垃圾回收孤立的查询文本。
 *
 * 在典型情况下，这个过程不会频繁调用，因为不太可能有太多的变动，而且，在关闭时将数据序列化到磁盘或重置时会发生类似的压缩过程。
 * 尽管如此，计划处理文件异常大的边缘情况是明智的，并且在可预见的未来可能不会出现其他压缩方法。
 *
 * 调用者必须持有 pgss->lock 的独占锁。
 *
 * 在出现问题的第一次迹象时，我们将查询文本文件取消链接，以获得一个干净的状态（尽管现有统计信息会保留），而不是冒险
 * 通过允许同样的问题无限次重现而导致性能崩溃。
 */
static void fc_gc_qtexts(void)
{
	char	   *fc_qbuffer;
	Size		fc_qbuffer_size;
	FILE	   *fc_qfile = NULL;
	HASH_SEQ_STATUS fc_hash_seq;
	pgssEntry  *fc_entry;
	Size		fc_extent;
	int			fc_nentries;

	/*
	 * 当从 pgss_store 调用时，其他会话可能在锁强度升级的无锁持有期间进行了垃圾回收。再一次检查这是否确实是必要的。
	 */
	if (!fc_need_gc_qtexts())
		return;

	/*
	 * 加载旧文本文件。如果我们失败（例如，内存不足），则使查询文本失效。希望这种情况很少发生。在 OOM 失败的情况下，似乎最好保持现状，
	 * 但问题在于，文件只会变得更大；希望将来没有 OOM 的结果风险极高，并且很容易导致完全拒绝服务。
	 */
	fc_qbuffer = fc_qtext_load_file(&fc_qbuffer_size);
	if (fc_qbuffer == NULL)
		goto gc_fail;

	/*
	 * 我们在原地覆盖查询文本文件，以降低磁盘空间不足失败的风险。由于文件被保证不会变大，这在传统文件系统上应该总是有效；
	 * 尽管我们仍然可能在写时复制的文件系统上失效。
	 */
	fc_qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
	if (fc_qfile == NULL)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m",
						PGSS_TEXT_FILE)));
		goto gc_fail;
	}

	fc_extent = 0;
	fc_nentries = 0;

	hash_seq_init(&fc_hash_seq, pgss_hash);
	while ((fc_entry = hash_seq_search(&fc_hash_seq)) != NULL)
	{
		int			fc_query_len = fc_entry->query_len;
		char	   *fc_qry = fc_qtext_fetch(fc_entry->query_offset,
									  fc_query_len,
									  fc_qbuffer,
									  fc_qbuffer_size);

		if (fc_qry == NULL)
		{
			/* 出现问题... 删除文本 */
			fc_entry->query_offset = 0;
			fc_entry->query_len = -1;
			/* 条目不会被计入平均查询长度计算 */
			continue;
		}

		if (fwrite(fc_qry, 1, fc_query_len + 1, fc_qfile) != fc_query_len + 1)
		{
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not write file \"%s\": %m",
							PGSS_TEXT_FILE)));
			hash_seq_term(&fc_hash_seq);
			goto gc_fail;
		}

		fc_entry->query_offset = fc_extent;
		fc_extent += fc_query_len + 1;
		fc_nentries++;
	}

	/*
	 * 修剪掉任何现在未使用的空间。如果由于某种奇怪的原因失败，我们会记录，但没有必要失败。
	 */
	if (ftruncate(fileno(fc_qfile), fc_extent) != 0)
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not truncate file \"%s\": %m",
						PGSS_TEXT_FILE)));

	if (FreeFile(fc_qfile))
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m",
						PGSS_TEXT_FILE)));
		fc_qfile = NULL;
		goto gc_fail;
	}

	elog(DEBUG1, "pgss gc of queries file shrunk size from %zu to %zu",
		 pgss->extent, fc_extent);

	/* 重置共享范围指针 */
	pgss->extent = fc_extent;

	/*
	 * 还要更新平均查询长度，以确保 need_gc_qtexts() 不会仍然认为我们存在问题。
	 */
	if (fc_nentries > 0)
		pgss->mean_query_len = fc_extent / fc_nentries;
	else
		pgss->mean_query_len = ASSUMED_LENGTH_INIT;

	free(fc_qbuffer);

	/*
	 * 好的，统计一次垃圾回收周期。 （注意：即使我们对 pgss->lock 拥有独占锁，我们仍然必须获取 pgss->mutex，因为其他进程可能在只持有 mutex 的情况下检查 gc_count。此外，我们必须在重写文件 *之后* 增加计数，否则其他进程可能不会意识到它们读取了一个过时的文件。）
	 */
	record_gc_qtexts();

	return;

gc_fail:
	/* 清理资源 */
	if (fc_qfile)
		FreeFile(fc_qfile);
	if (fc_qbuffer)
		free(fc_qbuffer);

	/*
	 * 由于外部文件的内容现在不确定，将所有哈希表条目标记为具有无效文本。
	 */
	hash_seq_init(&fc_hash_seq, pgss_hash);
	while ((fc_entry = hash_seq_search(&fc_hash_seq)) != NULL)
	{
		fc_entry->query_offset = 0;
		fc_entry->query_len = -1;
	}

	/*
	 * 销毁查询文本文件并创建一个新的空文件
	 */
	(void) unlink(PGSS_TEXT_FILE);
	fc_qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
	if (fc_qfile == NULL)
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not recreate file \"%s\": %m",
						PGSS_TEXT_FILE)));
	else
		FreeFile(fc_qfile);

	/* 重置共享范围指针 */
	pgss->extent = 0;

	/* 重置 mean_query_len 以匹配新状态 */
	pgss->mean_query_len = ASSUMED_LENGTH_INIT;

	/*
	 * 即使我们失败了，也要增加 GC 计数。
	 *
	 * 这对于使文件的并发读者在不加锁 pgss->lock 的情况下注意到文件新版本的存在是必要的。一旦读者随后在持有 pgss->lock 的情况下观察到 GC 计数的变化，这就强制安全重新打开文件。当然，写入者也要求我们在这里增加计数。（根据锁定协议的要求，读者和写入者在获取 pgss->lock 以共享或独占模式后，不会相信先前的文件内容，直到 gc_count 未变。）
	 */
	record_gc_qtexts();
}

/*
 * 释放与传递的参数对应的条目。
 */
static void fc_entry_reset(Oid fc_userid, Oid fc_dbid, uint64 fc_queryid)
{
	HASH_SEQ_STATUS fc_hash_seq;
	pgssEntry  *fc_entry;
	FILE	   *fc_qfile;
	long		fc_num_entries;
	long		fc_num_remove = 0;
	pgssHashKey fc_key;

	if (!pgss || !pgss_hash)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("pg_stat_statements must be loaded via shared_preload_libraries")));

	LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
	fc_num_entries = hash_get_num_entries(pgss_hash);

	if (fc_userid != 0 && fc_dbid != 0 && fc_queryid != UINT64CONST(0))
	{
		/* 如果所有参数都可用，使用快速路径。 */
		memset(&fc_key, 0, sizeof(pgssHashKey));
		fc_key.userid = fc_userid;
		fc_key.dbid = fc_dbid;
		fc_key.queryid = fc_queryid;

		/*
		 * 如果键存在，删除它，从非顶层条目开始。
		 */
		fc_key.toplevel = false;
		fc_entry = (pgssEntry *) hash_search(pgss_hash, &fc_key, HASH_REMOVE, NULL);
		if (fc_entry)				/* 找到了 */
			fc_num_remove++;

		/* 如果顶层条目存在，也删除它。 */
		fc_key.toplevel = true;
		fc_entry = (pgssEntry *) hash_search(pgss_hash, &fc_key, HASH_REMOVE, NULL);
		if (fc_entry)				/* 找到了 */
			fc_num_remove++;
	}
	else if (fc_userid != 0 || fc_dbid != 0 || fc_queryid != UINT64CONST(0))
	{
		/* 删除与有效参数对应的条目。 */
		hash_seq_init(&fc_hash_seq, pgss_hash);
		while ((fc_entry = hash_seq_search(&fc_hash_seq)) != NULL)
		{
			if ((!fc_userid || fc_entry->key.userid == fc_userid) &&
				(!fc_dbid || fc_entry->key.dbid == fc_dbid) &&
				(!fc_queryid || fc_entry->key.queryid == fc_queryid))
			{
				hash_search(pgss_hash, &fc_entry->key, HASH_REMOVE, NULL);
				fc_num_remove++;
			}
		}
	}
	else
	{
		/* 删除所有条目。 */
		hash_seq_init(&fc_hash_seq, pgss_hash);
		while ((fc_entry = hash_seq_search(&fc_hash_seq)) != NULL)
		{
			hash_search(pgss_hash, &fc_entry->key, HASH_REMOVE, NULL);
			fc_num_remove++;
		}
	}

	/* 所有条目都已删除？ */
	if (fc_num_entries != fc_num_remove)
		goto release_lock;

	/*
	 * 由于所有条目都已删除，重置 pg_stat_statements 的全局统计。
	 */
	{
		volatile pgssSharedState *fc_s = (volatile pgssSharedState *) pgss;
		TimestampTz fc_stats_reset = GetCurrentTimestamp();

		SpinLockAcquire(&fc_s->mutex);
		fc_s->stats.dealloc = 0;
		fc_s->stats.stats_reset = fc_stats_reset;
		SpinLockRelease(&fc_s->mutex);
	}

	/*
	 * 写入新的空查询文件，甚至在文件缺失时创建一个新的以进行恢复。
	 */
	fc_qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
	if (fc_qfile == NULL)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m",
						PGSS_TEXT_FILE)));
		goto done;
	}

	/* 如果 ftruncate 失败，记录它，但这不是致命问题 */
	if (ftruncate(fileno(fc_qfile), 0) != 0)
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not truncate file \"%s\": %m",
						PGSS_TEXT_FILE)));

	FreeFile(fc_qfile);

done:
	pgss->extent = 0;
	/* 在我们的目的上，这算作一次查询文本的垃圾回收 */
	record_gc_qtexts();

release_lock:
	LWLockRelease(pgss->lock);
}

/*
 * 生成一个规范化的查询字符串版本，用于表示所有类似的查询。
 *
 * 请注意，规范化表示可能会根据用于创建哈希表条目的“等效”查询而有所不同。我们假设这是可以的。
 *
 * 如果 query_loc > 0，则“query”相对于原始字符串起始位置已前移那么多，因此我们需要转换提供的位置以做补偿。（这让我们避免重新扫描感兴趣语句之前的语句，因此这样做是值得的。）
 *
 * *query_len_p 包含输入字符串的长度，并在退出时用结果字符串长度更新。结果字符串的长度可能会更长或更短，具体取决于常量替换的情况。
 *
 * 返回一个 palloc 的字符串。
 */
static char * fc_generate_normalized_query(JumbleState *fc_jstate, const char *fc_query,
						  int fc_query_loc, int *fc_query_len_p)
{
	char	   *fc_norm_query;
	int			fc_query_len = *fc_query_len_p;
	int			fc_i,
				fc_norm_query_buflen,	/* 允许的 norm_query 空间 */
				fc_len_to_wrt,		/* 要写入的长度（以字节为单位） */
				fc_quer_loc = 0,	/* 源查询字节位置 */
				fc_n_quer_loc = 0, /* 正常化查询字节位置 */
				fc_last_off = 0,	/* 前一个标记的起始偏移量 */
				fc_last_tok_len = 0;	/* 该标记的长度（以字节为单位） */

	/*
	 * 获取常量的长度（核心系统仅提供位置）。请注意
	 * 这也确保项按位置排序。
	 */
	fc_fill_in_constant_lengths(fc_jstate, fc_query, fc_query_loc);

	/*
	 * 允许$n符号比它们替换的常量更长。
	 * 常量在文本形式中至少占用一个字节，而$n符号
	 *肯定不会超过11个字节，即使n达到了INT_MAX。我们
	 *可以根据当前查询的n的最大值来细化这个限制，但似乎
	 *没有必要付出额外的努力来做到这一点。
	 */
	fc_norm_query_buflen = fc_query_len + fc_jstate->clocations_count * 10;

	/* 分配结果缓冲区 */
	fc_norm_query = palloc(fc_norm_query_buflen + 1);

	for (fc_i = 0; fc_i < fc_jstate->clocations_count; fc_i++)
	{
		int			fc_off,		/* 当前标记的起始偏移量 */
					fc_tok_len;	/* 该标记的长度（以字节为单位） */

		fc_off = fc_jstate->clocations[fc_i].location;
		/* 如果我们处理的是部分字符串，则调整记录的位置 */
		fc_off -= fc_query_loc;

		fc_tok_len = fc_jstate->clocations[fc_i].length;

		if (fc_tok_len < 0)
			continue;			/* 忽略任何重复项 */

		/* 复制下一个块（即下一个常量之前的内容） */
		fc_len_to_wrt = fc_off - fc_last_off;
		fc_len_to_wrt -= fc_last_tok_len;

		Assert(fc_len_to_wrt >= 0);
		memcpy(fc_norm_query + fc_n_quer_loc, fc_query + fc_quer_loc, fc_len_to_wrt);
		fc_n_quer_loc += fc_len_to_wrt;

		/* 并在常量标记的位置插入一个参数符号 */
		fc_n_quer_loc += sprintf(fc_norm_query + fc_n_quer_loc, "$%d",
							  fc_i + 1 + fc_jstate->highest_extern_param_id);

		fc_quer_loc = fc_off + fc_tok_len;
		fc_last_off = fc_off;
		fc_last_tok_len = fc_tok_len;
	}

	/*
	 * 我们已经复制到最后一个可忽略的常量。复制原始查询字符串
	 * 的剩余字节。
	 */
	fc_len_to_wrt = fc_query_len - fc_quer_loc;

	Assert(fc_len_to_wrt >= 0);
	memcpy(fc_norm_query + fc_n_quer_loc, fc_query + fc_quer_loc, fc_len_to_wrt);
	fc_n_quer_loc += fc_len_to_wrt;

	Assert(fc_n_quer_loc <= fc_norm_query_buflen);
	fc_norm_query[fc_n_quer_loc] = '\0';

	*fc_query_len_p = fc_n_quer_loc;
	return fc_norm_query;
}

/*
 * 给定一个有效的SQL字符串和一个常量位置记录数组，
 * 填充这些常量的文本长度。
 *
 * 常量可以使用任何允许的常量语法，例如浮点文字、
 * 位字符串、单引号字符串和美元引号字符串。这是
 * 通过使用核心扫描器的公共API来实现的。
 *
 * 确保字符串是有效的SQL语句，且常量位于指定位置
 * 的工作由调用者负责。由于在实践中字符串
 * 已经被解析，而调用者所提供的位置将
 * 源自权威解析器，因此这应该不是
 * 问题。
 *
 * 可能存在重复的常量指针，其长度
 * 将标记为'-1'，以便之后忽略它们。（实际上，我们假设
 * 长度最初已初始化为-1，并在此处不更改它们。）
 *
 * 如果query_loc > 0，则“query”与
 * 原始字符串起始位置相比已经向前推进了这么多，
 * 因此我们需要转换提供的位置以进行补偿。
 * （这使我们能够避免重新扫描感兴趣的语句之前的语句，因此是值得的。）
 *
 * 注意：假设在常量位置的'-'字符开始
 * 一个负数常量。这排除了常量以'-'开头
 * 的其他原因。
 */
static void fc_fill_in_constant_lengths(JumbleState *fc_jstate, const char *fc_query,
						 int fc_query_loc)
{
	LocationLen *fc_locs;
	core_yyscan_t fc_yyscanner;
	core_yy_extra_type fc_yyextra;
	core_YYSTYPE fc_yylval;
	YYLTYPE		fc_yylloc;
	int			fc_last_loc = -1;
	int			fc_i;

	/*
	 * 按位置对记录进行排序，以便我们可以在扫描查询文本时
	 * 按顺序处理它们。
	 */
	if (fc_jstate->clocations_count > 1)
		qsort(fc_jstate->clocations, fc_jstate->clocations_count,
			  sizeof(LocationLen), fc_comp_location);
	fc_locs = fc_jstate->clocations;

	/* 初始化flex扫描器 --- 应当与raw_parser()匹配 */
	fc_yyscanner = scanner_init(fc_query,
							 &fc_yyextra,
							 &ScanKeywords,
							 ScanKeywordTokens);

	/* 我们不想重新发出任何转义字符串警告 */
	fc_yyextra.escape_string_warning = false;

	/* 顺序查找每个常量 */
	for (fc_i = 0; fc_i < fc_jstate->clocations_count; fc_i++)
	{
		int			fc_loc = fc_locs[fc_i].location;
		int			fc_tok;

		/* 如果我们处理的是部分字符串，则调整记录的位置 */
		fc_loc -= fc_query_loc;

		Assert(fc_loc >= 0);

		if (fc_loc <= fc_last_loc)
			continue;			/* 重复常量，忽略 */

		/* 词法分析标记，直到找到所需的常量 */
		for (;;)
		{
			fc_tok = core_yylex(&fc_yylval, &fc_yylloc, fc_yyscanner);

			/* 我们不应触及字符串的结束，但如果我们这样做了，应该理智地处理 */  
			if (fc_tok == 0)
				break;			/* 离开内层for循环 */  

			/* 我们应该准确找到标记位置，但如果我们不小心超出了它，仍然要处理。 */  
			if (fc_yylloc >= fc_loc)
			{
				if (fc_query[fc_loc] == '-')
				{
					/* 这是一个负值——这是我们替换多个标记的唯一情况。  
 * 不要补偿核心系统对负常量中前导'-'运算符位置的特殊情况调整。  
 * 从负号开始对我们是有用的。  
 * 这样，像“select * from foo where bar = 1”和“select * from foo where bar = -2”的查询将具有相同的标准化查询字符串。 */  
					fc_tok = core_yylex(&fc_yylval, &fc_yylloc, fc_yyscanner);
					if (fc_tok == 0)
						break;	/* 离开内层for循环 */  
				}

				/* 我们现在依赖假设flex在scanbuf中当前标记的文本后放置了一个零字节。 */  
				fc_locs[fc_i].length = strlen(fc_yyextra.scanbuf + fc_loc);
				break;			/* 离开内层for循环 */  
			}
		}

		/* 如果我们触碰到字符串的结束，就放弃，留下剩余长度为-1 */  
		if (fc_tok == 0)
			break;

		fc_last_loc = fc_loc;
	}

	scanner_finish(fc_yyscanner);
}

/* comp_location：用于按照位置对LocationLen结构进行qsorting的比较器 */  
static int fc_comp_location(const void *fc_a, const void *fc_b)
{
	int			fc_l = ((const LocationLen *) fc_a)->location;
	int			fc_r = ((const LocationLen *) fc_b)->location;

	if (fc_l < fc_r)
		return -1;
	else if (fc_l > fc_r)
		return +1;
	else
		return 0;
}
